﻿using System;
using System.Diagnostics.Contracts;

namespace O1.IO.Storage
{
    public class StorageBlock
    {
        internal const int BlockSize = 524288;

        internal const int BlockSizeLog2 = 19;

        private byte[][][] storage;

        private long length;

        public StorageBlock(long size)
        {
            this.length = size;
            int blocks = unchecked((int)(size / BlockSize));
            if (blocks * BlockSize <= size)
            {
                blocks += 1;
            }

            var s = new byte[blocks][][];
            for (int i = 0; i < s.Length - 1; i++)
            {
                s[i] = new byte[BlockSize][];
            }

            Contract.Assume(blocks == s.Length);
            s[blocks - 1] = new byte[size % BlockSize][];
            this.storage = s;
        }

        public long Length
        {
            get
            {
                return this.length;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Performance",
            "CA1819:PropertiesShouldNotReturnArrays",
            Justification = "By design. This is an array.")]
        public byte[] this[long index]
        {
            get
            {
                return this.storage[(int)(index >> BlockSizeLog2)][(int)(index & (BlockSize - 1))];
            }

            set
            {
                this.storage[(int)(index >> BlockSizeLog2)][(int)(index & (BlockSize - 1))] = value;
            }
        }

        public void Expand(long newSize)
        {
            Contract.Ensures(this.length >= newSize);

            var size = this.length;
            if (newSize <= size)
            {
                return;
            }

            int newBlockCount = unchecked((int)(newSize / BlockSize));
            if (newBlockCount * BlockSize < newSize)
            {
                newBlockCount += 1;
            }

            Contract.Assume(newBlockCount > unchecked((int)(size / BlockSize)));

            var s = new byte[newBlockCount][][];
            for (var i = 0; i < newBlockCount - 1; i++)
            {
                s[i] = new byte[BlockSize][];
            }

            s[newBlockCount - 1] = new byte[unchecked((int)newSize % BlockSize)][];
            var t = this.storage;
            for (var i = 0; i < t.Length; i++)
            {
                Array.Copy(t[i], s[i], t[i].Length);
            }

            this.length = newSize;
            this.storage = s;
        }
    }
}
